/*
 * Copyright 2013-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package one.stand.eureka;

import com.google.common.collect.Lists;
import com.netflix.discovery.shared.Application;
import com.netflix.eureka.EurekaServerContext;
import com.netflix.eureka.EurekaServerContextHolder;
import com.netflix.eureka.registry.PeerAwareInstanceRegistry;
import lombok.extern.slf4j.Slf4j;
import one.stand.eureka.info.ApplicationInfoManagerHandler;
import one.stand.eureka.info.IServiceInfo;
import one.stand.eureka.info.LogResponse;
import one.stand.eureka.info.ServiceConfigReader;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.async.DeferredResult;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.ForkJoinPool;

/**
 * @author Spencer Gibb
 * @author Gang Li
 */
@Controller
@RequestMapping("${eureka.dashboard.path:/deploy}")
@Slf4j
public class EurekaDeploayController {

    @Autowired
    private IServiceInfo serviceInfo;
    @Autowired
    private ServiceConfigReader serviceConfigReader;
    @Autowired
    private ApplicationInfoManagerHandler applicationInfoManagerHandler;

    @RequestMapping(method = RequestMethod.GET)
    public String deploy(HttpServletRequest request, Map<String, Object> model, String key) {
        if (!StringUtils.equals(key, "asdkf23asdi23@0=")) {
            return "forward:/";
        } else {
            serviceConfigReader.reload();
            applicationInfoManagerHandler.populateBase(request, model);
            populateApps(model);
            return "eureka/deploy";
        }
    }

    @RequestMapping(value = "execute/detail", method = RequestMethod.GET)
    public String deployDetail(HttpServletRequest request, Map<String, Object> model, String name, String commandKey) {
        serviceConfigReader.reload();
        applicationInfoManagerHandler.populateBase(request, model);
        model.put("projectName", name);
        model.put("version", serviceConfigReader.getLastVersion(name));
        model.put("commands", serviceConfigReader.getCommands(name).keySet());
        model.put("instanceInfos", applicationInfoManagerHandler.getInstanceInfos(name));
        return "eureka/deploy_detail";
    }

    @RequestMapping(value = "execute", method = RequestMethod.GET)
    public ResponseEntity<?> execute(HttpServletRequest request, Map<String, Object> model, String name, String commandKey) {
        log.info("execute: {} {}", name, commandKey);
        try {
            serviceInfo.deploy(name, commandKey);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return ResponseEntity.ok("execute " + e.getClass().getSimpleName() + ":" + e.getMessage());
        }
        return ResponseEntity.ok(StringUtils.EMPTY);
    }

    @RequestMapping(value = "execute/log", method = RequestMethod.GET)
    public DeferredResult<ResponseEntity<LogResponse>> executeLog(HttpServletRequest request, Map<String, Object> model, String name, String commandKey) {
        DeferredResult<ResponseEntity<LogResponse>> deferredResult = new DeferredResult<>(200L);
        deferredResult.onTimeout(() ->
                deferredResult.setErrorResult(
                        ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT)
                                .body("Request timeout occurred.")));
        deferredResult.onError((Throwable t) -> {
            deferredResult.setErrorResult(
                    ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                            .body("An error occurred."));
        });
        ForkJoinPool.commonPool().submit(() -> {
            StringBuilder stringBuilder = new StringBuilder();
            String inline;
            for (int i = 0; i < 50; i++) {
                inline = serviceInfo.logs(name, commandKey);
                if (StringUtils.isBlank(inline)) {
                    break;
                }
                stringBuilder.append(inline);
            }
            LogResponse logResponse = new LogResponse();
            logResponse.setInstanceInfos(applicationInfoManagerHandler.getInstanceInfos(name));
            logResponse.setLog(stringBuilder.toString());
            deferredResult.setResult(ResponseEntity.ok(logResponse));
        });
        return deferredResult;
    }

    private PeerAwareInstanceRegistry getRegistry() {
        return getServerContext().getRegistry();
    }

    private EurekaServerContext getServerContext() {
        return EurekaServerContextHolder.getInstance().getServerContext();
    }

    private void populateApps(Map<String, Object> model) {
        Collection<ServiceConfigReader.ServiceConfig> serviceConfigs = serviceConfigReader.get();
        List<Application> sortedApplications = getRegistry().getSortedApplications();
        ArrayList<Map<String, Object>> apps = new ArrayList<>();
        for (ServiceConfigReader.ServiceConfig serviceConfig : serviceConfigs) {
            LinkedHashMap<String, Object> appData = new LinkedHashMap<>();
            apps.add(appData);

            String lastVersion = serviceConfigReader.getLastVersion(serviceConfig.getName());
            appData.put("lastVersion", lastVersion);
            Map<String, String> deploys = serviceConfigReader.getCommands(serviceConfig.getName());
            List<Map<String, String>> shellCommonds = Lists.newArrayList();
            for (Map.Entry<String, String> entry : deploys.entrySet()) {
                Map<String, String> map = new HashMap<>();
                map.put("name", entry.getKey());
                if (entry.getValue().startsWith("http://")) {
                    map.put("url", entry.getValue());
                } else {
                    map.put("url", "deploy/execute/detail?name=" + serviceConfig.getName() + "&commandKey=" + entry.getKey());
                }
                shellCommonds.add(map);
            }
            appData.put("deploys", shellCommonds);

            Map<String, Object> app = applicationInfoManagerHandler.get(sortedApplications, serviceConfig.getName(), lastVersion);
            appData.putAll(app);
        }
        model.put("apps", apps);
    }
}
